import { Middleware, MiddlewareAPI, Dispatch, AnyAction } from "redux";
import merge from 'merge-options-es5';
import { SaveLocalStorageMiddlewareOptions, LoadLocalStorageOptions, RemoveLocalStorageOptions } from '../types';

interface RegisteringNamespaces {
  [propName: string]: {
    shakeTimer: NodeJS.Timeout | null // 延迟设置定时器
  }
}
const registeringNamespaces: RegisteringNamespaces = {}; // 注册的命名空间
export function getRegName(iframeSrc: string, namespace: string): string {
  return `${iframeSrc} ${namespace}`;
}


// 获取内嵌框架的window
type GetIframeOptions = Required<Pick<SaveLocalStorageMiddlewareOptions, 'iframeId' | 'iframeSrc'>>;
interface GetIframeCallback {
  onload: (iframeWindow: Window) => void
  onerror: (err: string) => void
}
export function getIframe(options: GetIframeOptions, callback: GetIframeCallback): void {
  const { iframeId, iframeSrc } = options;
  const { onload, onerror } = callback;

  let iframe: HTMLIFrameElement | null = document.querySelector(`#${iframeId}`);
  if (iframe) {
    if (iframe.contentWindow) {
      onload(iframe.contentWindow);
    } else {
      onerror('无iframe contentWindow');
    }
  } else {
    iframe = document.createElement('iframe');
    iframe.id = iframeId;
    iframe.src = iframeSrc;
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    iframe.onload = function () {
      if (iframe && iframe.contentWindow) {
        onload(iframe.contentWindow);
      } else {
        onerror('无iframe contentWindow');
      }
    }
    iframe.onerror = function () {
      onerror('redux-local-storage用于存储数据的iframe加载失败');
    }
  }
}

// 排除指定项
export function handleExcludeItem(exclude: string[] | string[][] | undefined, data: AnyObject): AnyObject {
  if (Array.isArray(exclude) && exclude.length) { // 排除指定项
    if (Array.isArray(exclude[0])) {
      exclude.forEach((item: any) => {
        data = excludeItem(item as string[], data);
      });
    } else {
      data = excludeItem(exclude as string[], data);
    }
  }
  return data;
}
function excludeItem(exclude: string[], state: AnyObject): AnyObject {
  const newState: AnyObject = JSON.parse(JSON.stringify(state));
  exclude.reduce((obj, item, index) => {
    if (obj[item] !== undefined && index === exclude.length - 1) {
      delete obj[item];
      return {};
    }
    return obj[item];
  }, newState);
  return newState;
}

// 包含指定项
export function handleIncludeItem(include: string[] | string[][] | undefined, data: AnyObject): AnyObject {
  if (Array.isArray(include) && include.length) { // 包含指定项
    if (Array.isArray(include[0])) {
      const stateArray = (include as string[][]).map(item => includeItem(item, data));
      data = stateArray.reduce((obj: AnyObject, item) => {
        return merge(obj, item);
      }, {});
    } else {
      data = includeItem(include as string[], data);
    }
  }
  return data;
}
function includeItem(include: string[], state: AnyObject): AnyObject {
  let newState: AnyObject = {};
  include.reduce(([newObj, oldObj], item, index) => {
    if (oldObj[item] === undefined) { // 没有值 代表无效选项
      newState = {};
    } else if (index === include.length - 1) { // 最后一项 可以直接赋值
      newObj[item] = oldObj[item];
    } else if (typeof oldObj[item] === 'object' && (oldObj[item].__proto__ === undefined || oldObj[item].__proto__ === Object.prototype)) { // 是正确的对象
      return [newObj[item] = {}, oldObj[item]];
    } else { // 其它是无效数据
      newState = {};
    }
    return [{}, {}];
  }, [newState, state]);
  return newState;
}

interface MessageBind extends Pick<SaveLocalStorageMiddlewareOptions, 'dataDispose' | 'exclude' | 'include'> {
  type: 'getItem' | 'setItem' | 'removeItem',
  regName: string
  timer?: NodeJS.Timeout | null
  resolve?: (data?: any) => void
  reject?: (err?: any) => void
}
export function messageBind({ type, regName, timer, dataDispose, exclude, include, resolve, reject }: MessageBind): (event: MessageEvent) => void {
  return function (event: MessageEvent) {
    if (event.data.regName === regName) {
      // 加载数据
      if (type === 'getItem') {
        clearTimeout(Number(timer));
        if (event.data.type === 'getItemSuccessCallback') {
          let stateString: string | null = event.data.data;
          if (dataDispose && stateString) { // 处理加密或者其它操作
            try {
              stateString = dataDispose(stateString);
            } catch (err) {
              stateString = null;
              reject && reject(`load.dataDispose error ${err}`);
            }
          }

          if (stateString) { // 处理完之后有值 解析后返回即可
            try {
              let data: AnyObject = JSON.parse(stateString);
              data = handleExcludeItem(exclude, data);
              data = handleIncludeItem(include, data);
              resolve && resolve(data);
            } catch (err) {
              reject && reject(`load解析数据 error ${err}`);
            }
          } else {
            resolve && resolve(undefined);
          }
        } else if (event.data.type === 'getItemFailCallback') {
          reject && reject(event.data.data);
        }
      }

      // 设置数据
      else if (type === 'setItem') {
        if (event.data.type === 'setItemSuccessCallback') {
          resolve && resolve(event.data.data);
        } else if (event.data.type === 'setItemFailCallback') {
          reject && reject(event.data.data);
        }
      }

      // 删除数据
      else if (type === 'removeItem') {
        if (event.data.type === 'removeItemSuccessCallback') {
          resolve && resolve();
        } else if (event.data.type === 'removeItemFailCallback') {
          reject && reject(event.data.data);
        }
      }
    }
  }
}



const initialOptions = { // 默认值
  namespace: 'redux-local-storage',
  exclude: undefined,
  include: undefined,
  shake: undefined,
  iframeSrc: undefined,
  iframeId: undefined,
  timeout: 3000,
  dataDispose: undefined,
  onSuccess: undefined,
  onError: undefined,
};

// 存储数据中间件
interface StateDisposeProps extends Pick<SaveLocalStorageMiddlewareOptions, 'exclude' | 'include' | 'dataDispose' | 'onError'> {
  store: MiddlewareAPI<Dispatch<AnyAction>, any>
}
export function stateDispose({ store, exclude, include, dataDispose, onError }: StateDisposeProps): string | null { // 处理状态
  let state: AnyObject = store.getState();
  state = handleExcludeItem(exclude, state);
  state = handleIncludeItem(include, state);

  let stateString: string | null = JSON.stringify(state); // 处理存入的信息
  if (dataDispose) { // 处理加密或者其它操作
    try {
      stateString = dataDispose(stateString);
    } catch (err) {
      stateString = null;
      onError && onError(`save.dataDispose error ${err}`);
    }
  }

  return stateString;
}

interface SetLocalStorageProps extends Pick<SaveLocalStorageMiddlewareOptions, 'iframeId' | 'iframeSrc' | 'namespace' | 'onError' | 'onSuccess'> {
  stateString: string
  regName?: string
}
export function setLocalStorage({ stateString, iframeId, iframeSrc, namespace, regName, onError, onSuccess }: SetLocalStorageProps): void {
  if (iframeId && iframeSrc) { // 如果能存到子文档 扩容需要在不同域
    getIframe({ iframeId, iframeSrc }, {
      onload(iframeWindow) {
        const message = { type: 'setItem', namespace, regName, data: stateString, origin: window.location.origin };
        iframeWindow.postMessage(message, iframeSrc); // 发送消息
      },
      onerror(err) {
        onError && onError(err);
      }
    });
  } else { // 否则存到当前文档
    localStorage.setItem(namespace || '', stateString);
    onSuccess && onSuccess(stateString);
  }
}

export function saveLocalStorageMiddleware(options: SaveLocalStorageMiddlewareOptions = {}): Middleware {
  const {
    namespace = initialOptions.namespace,
    exclude = initialOptions.exclude,
    include = initialOptions.include,
    shake = initialOptions.shake,
    iframeId = initialOptions.iframeId,
    iframeSrc = initialOptions.iframeSrc,
    dataDispose = initialOptions.dataDispose,
    onSuccess = initialOptions.onSuccess,
    onError = initialOptions.onError,
  } = options;

  let shakeTimer: NodeJS.Timeout | null = null; // 延时保存用到的定时器

  const regName = getRegName(iframeSrc || '', namespace); // 注册名
  if (registeringNamespaces[regName]) {
    throw Error('saveLocalStorageMiddleware 在同一地址下，命名空间必须唯一');
  } else {
    registeringNamespaces[regName] = {
      shakeTimer: null,
    };
  }

  if (iframeId && iframeSrc) {
    window.addEventListener('message', messageBind({ type: 'setItem', regName, reject: onError, resolve: onSuccess }), false);
  }

  return store => next => action => {
    next(action);

    if (typeof shake === 'number' && !isNaN(shake)) {
      // 需要启用延迟写入clearTimeout(Number(shakeTimer));
      clearTimeout(Number(shakeTimer));
      registeringNamespaces[regName].shakeTimer = shakeTimer = setTimeout(() => {
        registeringNamespaces[regName].shakeTimer = null;
        const stateString = stateDispose({ store, exclude, include, dataDispose, onError });
        stateString && setLocalStorage({ stateString, iframeId, iframeSrc, namespace, regName, onError, onSuccess });
      }, shake);
    } else {
      const stateString = stateDispose({ store, exclude, include, dataDispose, onError });
      stateString && setLocalStorage({ stateString, iframeId, iframeSrc, namespace, regName, onError, onSuccess });
    }

    return action;
  }
}


// 加载默认数据
export function loadLocalStorage(options: LoadLocalStorageOptions = {}): Promise<AnyObject | undefined> {
  const {
    namespace = initialOptions.namespace,
    exclude = initialOptions.exclude,
    include = initialOptions.include,
    iframeSrc = initialOptions.iframeSrc,
    iframeId = initialOptions.iframeId,
    timeout = initialOptions.timeout,
    dataDispose = initialOptions.dataDispose,
  } = options;

  return new Promise((resolve, reject) => {
    if (iframeId && iframeSrc) { // 使用了内嵌框架 返回promise
      let timer: NodeJS.Timeout | null = null;

      const regName = getRegName(iframeSrc, namespace); // 注册名

      window.addEventListener('message', messageBind({ type: 'getItem', regName, timer, dataDispose, reject, resolve, exclude, include }), false);

      getIframe({ iframeId, iframeSrc }, {
        onload(iframeWindow) {
          timer = setTimeout(() => {
            reject('timeout');
          }, timeout);
          const message = { type: 'getItem', namespace, regName, origin: window.location.origin };
          iframeWindow.postMessage(message, iframeSrc);
        },
        onerror(err) {
          reject(err);
        }
      });
    } else {
      let stateString: string | null = localStorage.getItem(namespace);
      if (stateString && dataDispose) { // 处理加密或者其它操作
        try {
          stateString = dataDispose(stateString);
        } catch (err) {
          stateString = null;
          reject(err);
        }
      }

      if (stateString) { // 数据处理之后解析
        try {
          let data: AnyObject = JSON.parse(stateString);
          data = handleExcludeItem(exclude, data);
          data = handleIncludeItem(include, data);
          resolve(data);
        } catch (err) {
          reject(`load解析数据 error ${err}`);
        }
      } else {
        resolve(undefined);
      }
    }
  });
}


// 删除数据
export function removeLocalStorage(options: RemoveLocalStorageOptions = {}): Promise<void> {
  const {
    namespace = initialOptions.namespace,
    iframeId = initialOptions.iframeId,
    iframeSrc = initialOptions.iframeSrc,
  } = options;

  const regName = getRegName(iframeSrc || '', namespace); // 注册名
  if (registeringNamespaces[regName] && typeof registeringNamespaces[regName].shakeTimer === 'number') {
    // 如果开启了定时设置 将直接清除该定时器
    clearTimeout(Number(registeringNamespaces[regName].shakeTimer));
    registeringNamespaces[regName].shakeTimer = null;
  }

  return new Promise((resolve, reject) => {
    if (iframeId && iframeSrc) {
      window.addEventListener('message', messageBind({ type: 'removeItem', regName, resolve, reject }), false);

      getIframe({ iframeId, iframeSrc }, {
        onload(iframeWindow) {
          const message = { type: 'removeItem', namespace, regName, origin: window.location.origin };
          iframeWindow.postMessage(message, iframeSrc);
        },
        onerror(err) {
          reject(err);
        }
      });
    } else {
      localStorage.removeItem(namespace);
      resolve();
    }
  });
}
